package com.xunk.equipment.listener;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.google.common.collect.Sets;
import com.xunk.common.core.constant.CacheConstants;
import com.xunk.common.core.constant.DnakeConstants;
import com.xunk.common.core.constant.HttpStatus;
import com.xunk.common.core.constant.MessageNotifyConstants;
import com.xunk.common.core.enums.DeviceEnum;
import com.xunk.common.core.enums.SupplierEnum;
import com.xunk.common.core.enums.dnake.DnakeActionEnum;
import com.xunk.common.core.enums.dnake.DnakeDeviceTypeEnum;
import com.xunk.common.core.enums.dnake.DnakeErrnoEnum;
import com.xunk.common.core.enums.dnake.DnakeMsgTypeEnum;
import com.xunk.common.core.exception.CustomException;
import com.xunk.common.core.utils.DataUtils;
import com.xunk.common.core.utils.StringUtils;
import com.xunk.common.redis.service.RedisService;
import com.xunk.equipment.annotation.MqttListener;
import com.xunk.equipment.domain.EquipmentBaseInfoDO;
import com.xunk.equipment.domain.EquipmentDO;
import com.xunk.equipment.mapper.EquipmentMapper;
import com.xunk.equipment.mapper.HomeMapper;
import com.xunk.equipment.service.BaseService;
import com.xunk.equipment.service.acrel.service.PublishMessageComponent;
import com.xunk.equipment.service.dnake.vo.DnakeResponseMqttMessage;
import com.xunk.equipment.websocket.WebsocketManager;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.paho.client.mqttv3.IMqttMessageListener;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @ProjectName: xunk
 * @Package: com.xunk.equipment.listener
 * @ClassName: DnakeListener
 * @Author: wangTong
 * @Description: 狄耐克订阅topic
 * 作用：数据发布，用于app和网关之间的交互（主要包含控制消息和响应消息的发送）
 * 举例：app向网关发送控制设备的指令：指令---> /abc/g_01/update
 * 网关向app发送的响应消息:  响应消息--->/abc/a_02/update
 * @Date: 2021/5/10 19:02
 * @Version: 1.0
 */
@Slf4j
@Service
@MqttListener(topic = "/+/+/update", type = 2)
public class DnakeUpdateListener implements IMqttMessageListener {

    @Resource(name = "asyncMqttTaskExecutor")
    ThreadPoolTaskExecutor threadPoolTaskExecutor;
    @Autowired
    RedisService redisService;
    @Autowired
    BaseService baseService;
    @Autowired
    HomeMapper homeMapper;
    @Autowired
    EquipmentMapper equipmentMapper;
    @Autowired
    PublishMessageComponent publishMessageComponent;

    private static Set<String> excludeFieldSets = Sets.newHashSet("devCh", "devNo", "devType");

    @Override
    public void messageArrived(String topic, MqttMessage message) {
        threadPoolTaskExecutor.execute(() -> {
            DnakeResponseMqttMessage response = JSONObject.parseObject(new String(message.getPayload()),
                    new TypeReference<DnakeResponseMqttMessage>() {
                    });
            log.info("=========== {}", JSON.toJSONString(response));

            String uuid = response.getData().getUuid();
            Boolean aBoolean = redisService.tryLock(uuid, "1", 10, false);
            if (!aBoolean) {
                return;
            }
            String[] topics = topic.split("/");

            // 设备相应信息
            if ("ok".equals(response.getData().getResult())) {
                // 操作成功

                // 对于toDev是system是系统发的定时查询网关子设备状态信息
                if (DnakeConstants.SYSTEM.equals(response.getToDev())) {
                    // 更新影子信息
                    List<Map<String, Object>> devList = response.getData().getDevList();
                    if (null != devList) {
                        Map<Integer, List<Map<String, Object>>> devMaps = devList.stream().collect(Collectors.groupingBy(m -> (Integer) m.get("devNo")));
                        String gatewaySn = topics[2];
                        devMaps.forEach((k, v) -> {
                            Map<String, Object> m = v.get(0);
                            String deviceSn = gatewaySn + "-" + k;
                            EquipmentDO equipmentDO = baseService.getEquipmentInfo(deviceSn, false);
                            Long deptId;
                            if (DataUtils.isEmpty(equipmentDO)) {
                                EquipmentDO equipmentInfo = baseService.getEquipmentInfo(gatewaySn);
                                String devType = m.get("devType").toString();
                                String variantDevType = devType + "-" + v.size();
                                DnakeDeviceTypeEnum typeEnum = DnakeDeviceTypeEnum.getDeviceTypeEnumByCode(variantDevType);
                                if (Objects.equals(typeEnum, DnakeDeviceTypeEnum.UNDEFINED)) {
                                    return;
                                }
                                EquipmentBaseInfoDO deviceBaseInfo = equipmentMapper.getDeviceBaseInfo(variantDevType, SupplierEnum.DNAKE.getSupplierName());
                                equipmentDO = new EquipmentDO();
                                equipmentDO.setDeviceSn(deviceSn);

                                String deviceName;
                                Integer deviceSource = 0;
                                Integer deviceCategory = 2;
                                Long deviceBaseId = null;

                                if (DataUtils.isEmpty(deviceBaseInfo)) {
                                    deviceName = typeEnum.getDesc();
                                } else {
                                    deviceName = deviceBaseInfo.getBaseName();
                                    deviceSource = deviceBaseInfo.getBaseSource();
                                    deviceCategory = deviceBaseInfo.getBaseCategory();
                                    deviceBaseId = deviceBaseInfo.getId();
                                }

                                equipmentDO.setDeviceName(deviceName);
                                equipmentDO.setDeviceNickname(equipmentDO.getDeviceName());
                                deptId = equipmentInfo.getDeptId();
                                equipmentDO.setDeptId(deptId);
                                // 默认家庭逻辑
                                equipmentDO.setDeviceSource(deviceSource);
                                // 网关子设备
                                equipmentDO.setDeviceCategory(deviceCategory);
                                equipmentDO.setDeviceType(typeEnum.getDeviceEnum().getDeviceType());
                                equipmentDO.setBrand("狄耐克");
                                equipmentDO.setDeviceOnline(1L);
                                equipmentDO.setSupplier(SupplierEnum.DNAKE.getSupplierName());
                                equipmentDO.setDeviceBaseId(deviceBaseId);
                                equipmentDO.setParentSn(gatewaySn);
                                equipmentDO.setCreateTime(new Date());
                                equipmentDO.setCreateBy("0");
                                Map<String, String> channelNickname = new HashMap<>();
                                for (int i = 1; i <= v.size(); i++) {
                                    channelNickname.put(i + "", "通道" + i);
                                }
                                equipmentDO.setChannelNickname(JSON.toJSONString(channelNickname));
                                try {
                                    equipmentMapper.insertEquipment(equipmentDO);
                                } catch (Exception e) {
                                    log.error("异常 uuid = {}", uuid, e);
                                }
                            } else {
                                deptId = equipmentDO.getDeptId();
                            }
                            int size = v.size();
                            Map<String, Object> cache = new HashMap<>();
                            cache.put("channels", size);
                            v.forEach(dev -> {
                                String devCh = dev.get("devCh").toString();
                                dev.forEach((k1, v1) -> {
                                    if (!excludeFieldSets.contains(k1)) {
                                        cache.put(devCh + "." + k1, v1);
                                    }
                                });
                            });
                            redisInfo(deptId, deviceSn, cache);
                        });
                    }
                }
                // 对于toDev是online是系统发的定时查询网关子设备在线状态信息
                if (DnakeConstants.ONLINE.equals(response.getToDev())) {
                    List<Map<String, Object>> devList = response.getData().getDevList();
                    if (null != devList) {

                        devList.forEach(device -> {
                            String deviceSn = topics[2] + "-" + device.get("devNo");
                            try {
                                EquipmentDO equipmentInfo = baseService.getEquipmentInfo(deviceSn);
                                // 更新设备影子
                                redisInfo(equipmentInfo.getDeptId(), deviceSn, "online", device.get("online").toString());
                                redisInfo(equipmentInfo.getDeptId(), deviceSn, "time", Integer.valueOf(device.get("time").toString()));

                                // 更新数据库
                                equipmentMapper.updateEquipmentOnline(deviceSn, Long.parseLong(device.get("online").toString()));
                            } catch (CustomException ignored) {
                                ignored.printStackTrace();
                            }
                        });
                    }
                }
                // 对于toDev是defence是布撤防区命令
                if (DnakeConstants.DEFENCE.equals(response.getToDev())) {
                    Map<String, Object> map = redisService.getCacheObject(CacheConstants.DNAKE_CONTROL_INFO + uuid);
                    String deviceSn = map.get("deviceSn").toString();
                    Long deptId = Long.valueOf(map.get("deptId").toString());
                    String value = map.get("value").toString();
                    int v;
                    if (Objects.equals("on", value)) {
                        v = 1;
                    } else if (Objects.equals("off", value)) {
                        v = 0;
                    } else {
                        return;
                    }
                    equipmentMapper.updateArming(deviceSn, v);

                    Map<String, Object> propertyMap = new HashMap<>();
                    propertyMap.put("arming", value);
                    // 查找当前home对应的user
                    List<Long> userIds = homeMapper.listUserIdsByDeptId(deptId);
                    Map<String, Object> data = new HashMap<>();
                    data.put("deviceSn", deviceSn);
                    data.put("properties", propertyMap);
                    data.put("deviceType", DeviceEnum.DOOR_WINDOW_SENSOR.getDeviceType());

                    Map<String, Object> pr = new HashMap<>();
                    pr.put("code", HttpStatus.SUCCESS);
                    pr.put("msg", "设备消息");
                    pr.put("type", MessageNotifyConstants.PROPERTY_NOTIFY_TYPE);
                    pr.put("data", data);
                    // 发送属性变化的推送
                    userIds.forEach(userId -> WebsocketManager.appointSending(String.valueOf(userId), JSON.toJSONString(pr)));
                }
                return;
            }
            if ("no".equals(response.getData().getResult())) {
                // 错误描述
                String errDesc = DnakeErrnoEnum.getDescByCode(response.getData().getErrno());

                Map<String, Object> map = redisService.getCacheObject(CacheConstants.DNAKE_CONTROL_INFO + uuid);
                String deviceSn = map.get("deviceSn").toString();
                String userId = (String) map.get("userId");
                if (DataUtils.isEmpty(userId)) {
                    return;
                }
                EquipmentDO equipmentInfo = baseService.getEquipmentInfo(deviceSn);

                // 调用webSocket通知
                WebsocketManager.appointSending(userId, getSocketMessage(equipmentInfo, HttpStatus.ERROR, errDesc, 3));
                return;
            }

            // 设备主动推送信息
            if (StringUtils.isNotEmpty(response.getData().getAction())) {
                // 网关主动推送设备信息
                if (DnakeActionEnum.CMTDEVINFO.getCode().equals(response.getData().getAction())) {
                    String deviceSn = topics[2] + "-" + response.getData().getDevNo();
                    Integer msgType = response.getData().getMsgType();
                    Integer msg = response.getData().getMsg();
                    EquipmentDO equipmentInfo = baseService.getEquipmentInfo(deviceSn);
                    if (DataUtils.isEmpty(equipmentInfo)) {
                        return;
                    }
                    // 删除设备 msgType = 0, msg = 2
                    if (Objects.equals(msgType, 0) && Objects.equals(msg, 2)) {
                        String ancestor = baseService.findAncestorByDeptId(equipmentInfo.getDeptId());
                        redisService.deleteObject(ancestor.concat(":").concat(deviceSn));
                        redisService.deleteObject(CacheConstants.EQUIPMENT_INFO_KEY.concat(deviceSn));
                        String parentSn = response.getFromDev();
                        equipmentMapper.deleteEquipment(deviceSn, parentSn);
                    } else {
                        // 其他设备信息
                        List<Long> userIds = homeMapper.listUserIdsByDeptId(equipmentInfo.getDeptId());
                        userIds.forEach(userId ->
                                WebsocketManager.appointSending(String.valueOf(userId),
                                        getSocketMessage(equipmentInfo,
                                                HttpStatus.SUCCESS,
                                                3,
                                                String.valueOf(response.getData().getDevCh()),
                                                getMsgVOFromMsgType(msgType, msg))));
                        // 更新设备影子
                        redisInfo(equipmentInfo.getDeptId(), deviceSn, response.getData().getDevCh() + "." + DnakeMsgTypeEnum.getKeyByMsgType(msgType), msg);
                    }
                    return;
                }
                // 告警信息
                if (DnakeActionEnum.CMTALARM.getCode().equals(response.getData().getAction())) {
                    String deviceSn = topics[2] + "-" + response.getData().getDevNo();

                    EquipmentDO equipmentInfo = baseService.getEquipmentInfo(deviceSn);
                    if (DataUtils.isEmpty(equipmentInfo)) {
                        return;
                    }
                    if (Objects.equals(response.getData().getAlarmType(), 0)) {
                        // 入库记录告警信息、发送webSocket
                        publishMessageComponent.saveAlarmInfo(deviceSn, "尊敬的用户，您的家庭“%s”中‘%s’设备发出告警，请及时检查家庭安全情况~", "安防报警");
                    }

                }
            }
            redisService.deleteObject(uuid);
        });
    }

    /**
     * 更新设备影子
     *
     * @param deptId   家庭id/社区园区id
     * @param deviceSn 设备号
     * @param hKey     hKey
     * @param value    value
     */
    private void redisInfo(Long deptId, String deviceSn, String hKey, Integer value) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        redisService.setCacheMapValue(key, hKey, value);
    }

    /**
     * 更新设备影子
     *
     * @param deptId   家庭id/社区园区id
     * @param deviceSn 设备号
     * @param hKey     hKey
     * @param value    value
     */
    private void redisInfo(Long deptId, String deviceSn, String hKey, String value) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        redisService.setCacheMapValue(key, hKey, value);
    }

    /**
     * 存放设备影子
     *
     * @param deptId   家庭id/社区园区id
     * @param deviceSn 设备号
     * @param shadows  设备影子
     */
    private void redisInfo(Long deptId, String deviceSn, Map<String, Object> shadows) {
        String ancestor = baseService.findAncestorByDeptId(deptId);
        String key = ancestor.concat(":").concat(deviceSn);
        redisService.setCacheMap(key, shadows);
    }

    /**
     * 翻译消息
     *
     * @param msgType 消息类型
     * @param msg     消息
     * @return
     */
    private String getMsgVOFromMsgType(Integer msgType, Integer msg) {
        switch (msgType) {
            case 0:
                switch (msg) {
                    case 0:
                        return "off";
                    case 1:
                        return "on";
                    case 2:
                        return "delete";
                    case 3:
                        return "stop";
                    default:
                        return "unknow";
                }
            case 8:
                switch (msg) {
                    case 0:
                        return "未发现";
                    case 1:
                        return "发现";
                    default:
                        return "未知";
                }
            case 10:
                switch (msg) {
                    case 0:
                        return "off";
                    case 1:
                        return "Auto";
                    case 2:
                        return "Cool";
                    case 3:
                        return "Heat";
                    case 4:
                        return "Emergency";
                    case 5:
                        return "heating";
                    case 6:
                        return "Precooling";
                    case 7:
                        return "Fan only";
                    case 8:
                        return "Dry";
                    case 9:
                        return "Sleep";
                    default:
                        return "未知";
                }
            case 11:
                switch (msg) {
                    case 0:
                        return "off";
                    case 1:
                        return "low";
                    case 2:
                        return "Medium";
                    case 3:
                        return "High";
                    case 4:
                        return "On";
                    case 5:
                        return "Auto";
                    default:
                        return "未知";
                }
            case 19:
                switch (msg) {
                    case 1:
                        return "低";
                    case 2:
                        return "正常";
                    case 3:
                        return "高";
                    default:
                        return "未知";
                }
            case 22:
                switch (msg) {
                    case 1:
                        return "好";
                    case 2:
                        return "中";
                    case 3:
                        return "差";
                    default:
                        return "未知";
                }
            default:
                return String.valueOf(msg);
        }
    }

    /**
     * 拼接socket信息
     *
     * @param equipmentInfo 设备信息
     * @param code          返回状态码
     * @param msg           返回信息
     * @param type          类型：1 系统消息；2 设备告警消息；3 设备控制；
     * @return socket信息
     */
    private String getSocketMessage(EquipmentDO equipmentInfo, int code, String msg, int type) {
        //
        Map<String, Object> socketMessage = new HashMap<>();
        socketMessage.put("code", code);
        socketMessage.put("msg", msg);
        socketMessage.put("type", type);
        Map<String, Object> data = new HashMap<>();
        data.put("deviceName", equipmentInfo.getDeviceName());
        data.put("deviceSn", equipmentInfo.getDeviceSn());
        data.put("deviceSource", equipmentInfo.getDeviceSource());
        data.put("deviceType", equipmentInfo.getDeviceType());
//            data.put("homeId", );
//            data.put("roomId", equipmentInfo.getRoomNum());
//            data.put("roomName", 0);
        data.put("supplier", equipmentInfo.getSupplier());
        socketMessage.put("data", data);
        return JSON.toJSONString(socketMessage);
    }

    private String getSocketMessage(EquipmentDO equipmentInfo, int code, int type, String devCh, String on) {
        //
        Map<String, Object> socketMessage = new HashMap<>();
        socketMessage.put("code", code);
        socketMessage.put("type", type);
        Map<String, Object> data = new HashMap<>();
        data.put("deviceName", equipmentInfo.getDeviceName());
        data.put("deviceSn", equipmentInfo.getDeviceSn());
        data.put("deviceSource", equipmentInfo.getDeviceSource());
        data.put("deviceType", equipmentInfo.getDeviceType());

        Map<String, String> properties = new HashMap<>();
        properties.put("devCh", devCh);
        properties.put("on", on);
        data.put("properties", properties);
        data.put("supplier", equipmentInfo.getSupplier());
        socketMessage.put("data", data);
        return JSON.toJSONString(socketMessage);
    }

}
